﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Resources;

namespace PLock {
	partial class ConfigDlg : Form {
		public ConfigDlg() {
			InitializeComponent();
			textBoxFolder.Text = Settings.Instance.DataFolder;
			numericUpDownPassLength.Value = Settings.Instance.GeneratedPassLength;
			checkBox09.Checked = Settings.Instance.GeneratePass09;
			checkBoxAZLower.Checked = Settings.Instance.GeneratePassAZLower;
			checkBoxAZUpper.Checked = Settings.Instance.GeneratePassAZUpper;
			checkBoxSymbols.Checked = Settings.Instance.GeneratePassSymbols;
			checkBoxSync.Checked = Settings.Instance.SyncExternal;
			radioButtonStandalone.Checked = Settings.Instance.SyncStandalone;
			radioButtonSlave.Checked = !Settings.Instance.SyncStandalone;
			LoadRemovableDrives(Settings.Instance.SyncDrive);
			if (Settings.Instance.Valid) {
				buttonChangeMaster.Enabled = File.Exists(Settings.Instance.FullPath(PasswordDlg.MasterDigestFile));
				this.WindowState = FormWindowState.Minimized;
				SyncExternal();
			}

			if (Settings.Instance.Portable) {
				checkBoxSync.Checked = false;
				checkBoxSync.Enabled = false;
			}
		}

		private bool _exiting = false;
		private CopyDlg _copydlg = null;

		#region Removable Drive Listing, Event Handling
		private void LoadRemovableDrives() {
			string selected = null;
			if (comboBoxDrives.SelectedItem != null)
				selected = comboBoxDrives.SelectedItem.ToString();
			LoadRemovableDrives(selected);
		}
		private void LoadRemovableDrives(string selected) {
			comboBoxDrives.Items.Clear();
			foreach (DriveInfo di in DriveInfo.GetDrives()) {
				if (di.DriveType == DriveType.Removable)
					comboBoxDrives.Items.Add(di.ToString());
			}
			if (selected != null) {
				foreach (string item in comboBoxDrives.Items) {
					if (item == selected) {
						comboBoxDrives.SelectedItem = item;
						return;
					}
				}
				if (checkBoxSync.Checked) {
					comboBoxDrives.Items.Add(selected);
					comboBoxDrives.SelectedItem = selected;
				}
			} else if (comboBoxDrives.Items.Count > 0)
				comboBoxDrives.SelectedIndex = 0;
		}

		private static int WM_DEVICECHANGE = 0x219;
		private static IntPtr DBT_DEVICEARRIVAL = new IntPtr(0x8000);
		private static IntPtr DBT_DEVICEREMOVECOMPLETE = new IntPtr(0x8004);
		private static int DBT_DEVTYP_VOLUME = 0x00000002;
		struct DEV_BROADCAST_HDR {
			public DEV_BROADCAST_HDR(int i) {
				dbch_size = 0;
				dbch_devicetype = 0;
				dbch_reserved = 0;
			}
			public int dbch_size;
			public int dbch_devicetype;
			public int dbch_reserved;
		}
		struct DEV_BROADCAST_VOLUME {
			public DEV_BROADCAST_VOLUME(int i) {
				dbcv_size = 0;
				dbcv_devicetype = 0;
				dbcv_reserved = 0;
				dbcv_unitmask = 0;
				dbcv_flags = 0;
			}
			public int dbcv_size;
			public int dbcv_devicetype;
			public int dbcv_reserved;
			public int dbcv_unitmask;
			public short dbcv_flags;
		}
		protected override void WndProc(ref Message m) {
			if (m.Msg == WM_DEVICECHANGE) {
				if (m.WParam == DBT_DEVICEARRIVAL || m.WParam == DBT_DEVICEREMOVECOMPLETE)
					LoadRemovableDrives();
				if ((m.WParam == DBT_DEVICEARRIVAL && Settings.Instance.SyncExternal) ||
					(m.WParam == DBT_DEVICEREMOVECOMPLETE && Settings.Instance.Portable)) {
					DEV_BROADCAST_HDR hdr = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
					if (hdr.dbch_devicetype == DBT_DEVTYP_VOLUME) {
						DEV_BROADCAST_VOLUME vol = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
						if (m.WParam == DBT_DEVICEARRIVAL) {
							if ((vol.dbcv_unitmask & Settings.Instance.SyncDriveBit) != 0)
								SyncExternal();
						} else {
							if ((vol.dbcv_unitmask & Settings.Instance.DataDriveBit) != 0)
								Exit();
						}
					}
				}
			}
			base.WndProc(ref m);
		}
		#endregion

		#region UI Callbacks
		private void buttonBrowse_Click(object sender, EventArgs e) {
			DialogResult res = folderBrowserDialog.ShowDialog(this);
			if (res == DialogResult.OK)
				textBoxFolder.Text = folderBrowserDialog.SelectedPath;
		}

		private void buttonOK_Click(object sender, EventArgs e) {
			string olddatafolder = Settings.Instance.DataFolder;
			Settings.Instance.DataFolder = textBoxFolder.Text;

			if (!File.Exists(Settings.Instance.FullPath(PasswordDlg.MasterDigestFile))) {
				try {
					using (MasterSetDlg dlg = new MasterSetDlg(false)) {
						if (dlg.ShowDialog(this) != DialogResult.OK) {
							Settings.Instance.DataFolder = olddatafolder;
							return;
						} else
							buttonChangeMaster.Enabled = true;
					}
				} catch (Exception ex) {
					MessageBox.Show(this, ex.Message, "Error writing digest file", MessageBoxButtons.OK, MessageBoxIcon.Error);
					return;
				}
			}

			Settings.Instance.GeneratedPassLength = (int)numericUpDownPassLength.Value;
			Settings.Instance.GeneratePass09 = checkBox09.Checked;
			Settings.Instance.GeneratePassAZLower = checkBoxAZLower.Checked;
			Settings.Instance.GeneratePassAZUpper = checkBoxAZUpper.Checked;
			Settings.Instance.GeneratePassSymbols = checkBoxSymbols.Checked;
			Settings.Instance.SyncExternal = checkBoxSync.Checked;
			Settings.Instance.SyncStandalone = radioButtonStandalone.Checked;
			if (Settings.Instance.SyncExternal) {
				if (comboBoxDrives.SelectedItem == null)
					Settings.Instance.SyncDrive = "";
				else
					Settings.Instance.SyncDrive = comboBoxDrives.SelectedItem.ToString();
			} else
				Settings.Instance.SyncDrive = "";

			Settings.Instance.Save();

			LabelCache.InvalidateCache();
			SyncExternal();

			this.Hide();
		}

		private void buttonCancel_Click(object sender, EventArgs e) {
			if (!Settings.Instance.Valid)
				Exit();
			else
				this.Hide();
		}

		private void UpdateOKEnabled() {
			int ind = comboBoxDrives.SelectedIndex;
			buttonOK.Enabled = textBoxFolder.Text != "" && (!checkBoxSync.Checked || comboBoxDrives.SelectedIndex != -1);
		}

		private void textBoxFolder_TextChanged(object sender, EventArgs e) {
			UpdateOKEnabled();
		}

		private void checkBoxSync_CheckedChanged(object sender, EventArgs e) {
			comboBoxDrives.Enabled = checkBoxSync.Checked;
			radioButtonSlave.Enabled = checkBoxSync.Checked;
			radioButtonStandalone.Enabled = checkBoxSync.Checked;
			UpdateOKEnabled();
		}

		private void configureToolStripMenuItem_Click(object sender, EventArgs e) {
			LoadRemovableDrives();
			this.WindowState = FormWindowState.Normal;
			UpdateOKEnabled();
			this.Show();
		}

		private void ConfigDlg_FormClosing(object sender, FormClosingEventArgs e) {
			if (!_exiting && e.CloseReason == CloseReason.UserClosing) {
				e.Cancel = true;
				buttonCancel_Click(sender, e);
			}
		}

		private void exitToolStripMenuItem_Click(object sender, EventArgs e) {
			Exit();
		}

		private void newToolStripMenuItem_Click(object sender, EventArgs e) {
			if (!PasswordDlg.PromptForPassword())
				return;

			Entry newentry = new Entry();
			using (EditDlg dlg = new EditDlg(newentry, true)) {
				if (dlg.ShowDialog(this) == DialogResult.OK)
					SyncExternal();
			}
		}

		private void clearClipboardToolStripMenuItem_Click(object sender, EventArgs e) {
			Clipboard.Clear();
		}

		private void forgetMasterPasswordToolStripMenuItem_Click(object sender, EventArgs e) {
			PasswordDlg.ForgetPassword();
		}

		private void contextMenuStrip_Opening(object sender, CancelEventArgs e) {
			deleteToolStripMenuItem.DropDownItems.Clear();
			editToolStripMenuItem.DropDownItems.Clear();
			copyToClipboardToolStripMenuItem.DropDownItems.Clear();

			newToolStripMenuItem.Enabled = Settings.Instance.Valid && Settings.Instance.Standalone;
			forgetMasterPasswordToolStripMenuItem.Enabled = PasswordDlg.HasPassword;
			configureToolStripMenuItem.Enabled = Settings.Instance.Standalone;

			string[] labels = LabelCache.GetLabels();
			if (labels.Length > 0) {
				if (Settings.Instance.Standalone) {
					deleteToolStripMenuItem.Enabled = true;
					editToolStripMenuItem.Enabled = true;
				} else {
					deleteToolStripMenuItem.Enabled = false;
					editToolStripMenuItem.Enabled = false;
				}
				copyToClipboardToolStripMenuItem.Enabled = true;
				foreach (string label in labels) {
					if (Settings.Instance.Standalone) {
						ToolStripMenuItem deleteitem = new ToolStripMenuItem(label);
						deleteitem.Click += new DeleteHandler(label, this).Click;
						deleteToolStripMenuItem.DropDownItems.Add(deleteitem);
						ToolStripMenuItem edititem = new ToolStripMenuItem(label);
						edititem.Click += new EditHandler(label, this).Click;
						editToolStripMenuItem.DropDownItems.Add(edititem);
					}
					string[] caps = LabelCache.GetCaps(label);
					if (caps.Length == 1) {
						foreach (string cap in caps) {
							ToolStripMenuItem copyitem = new ToolStripMenuItem(string.Format("{0} ({1})", label, CapLabel(cap)));
							copyitem.Click += new CopyHandler(label, cap).Click;
							copyToClipboardToolStripMenuItem.DropDownItems.Add(copyitem);
						}
					} else {
						ToolStripMenuItem copymenu = new ToolStripMenuItem(label);
						foreach (string cap in caps) {
							ToolStripMenuItem copyitem = new ToolStripMenuItem(CapLabel(cap));
							copyitem.Click += new CopyHandler(label, cap).Click;
							copymenu.DropDownItems.Add(copyitem);
						}
						copyToClipboardToolStripMenuItem.DropDownItems.Add(copymenu);
					}
				}
			} else {
				deleteToolStripMenuItem.Enabled = false;
				editToolStripMenuItem.Enabled = false;
				copyToClipboardToolStripMenuItem.Enabled = false;
			}
		}

		private void notifyIcon_MouseDown(object sender, MouseEventArgs e) {
			if (e.Button != MouseButtons.Left) return;

			if (_copydlg == null) {
				_copydlg = new CopyDlg();
				_copydlg.LoadPosition();
			}
			if (_copydlg.Visible)
				_copydlg.Hide();
			else {
				_copydlg.SetItems();
				_copydlg.Show();
			}
		}

		private void buttonChangeMaster_Click(object sender, EventArgs e) {
			if (MessageBox.Show(this, "Have you made a backup of your data folder?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
				== DialogResult.No) {
				MessageBox.Show(this, "Back up the data folder before changing your master password.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Hand);
				return;
			}
			if (PasswordDlg.PromptForPassword("Enter Current Master Password")) {
				using (MasterSetDlg dlg = new MasterSetDlg(true)) {
					dlg.ShowDialog(this);
				}
			}
		}
		#endregion

		#region Context Menu Callbacks
		class DeleteHandler {
			public DeleteHandler(string label, ConfigDlg parent) {
				_label = label;
				_parent = parent;
			}

			private string _label;
			private ConfigDlg _parent;

			public void Click(object sender, EventArgs e) {
				if (MessageBox.Show("Are you sure you want to delete '" + _label + "'?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning)
					== DialogResult.No)
					return;
				try {
					LabelCache.GetEntry(_label).Delete();
				} catch (Exception ex) {
					MessageBox.Show(ex.Message, "Error Deleting Entry", MessageBoxButtons.OK, MessageBoxIcon.Error);
				} finally {
					LabelCache.InvalidateCache();
					_parent.SyncExternal();
				}
			}
		}

		class EditHandler {
			public EditHandler(string label, ConfigDlg parent) {
				_label = label;
				_parent = parent;
			}

			private string _label;
			private ConfigDlg _parent;

			public void Click(object sender, EventArgs e) {
				if (!PasswordDlg.PromptForPassword())
					return;
				using (EditDlg dlg = new EditDlg(LabelCache.GetEntry(_label), false)) {
					if (dlg.ShowDialog() == DialogResult.OK)
						_parent.SyncExternal();
				}
			}
		}

		class CopyHandler {
			public CopyHandler(string label, string cap) {
				_label = label;
				_cap = cap;
			}

			private string _label;
			private string _cap;

			public void Click(object sender, EventArgs e) {
				ConfigDlg.CopyToClipboard(_label, _cap);
			}
		}
		#endregion

		private void Exit() {
			_exiting = true;
			notifyIcon.Visible = false;
			if (_copydlg != null) {
				_copydlg.SavePosition();
				_copydlg.Dispose();
			}
			Application.Exit();
		}

		public static bool CopyToClipboard(string label, string cap) {
			if (!PasswordDlg.PromptForPassword())
				return false;
			string value;
			try {
				value = LabelCache.GetEntry(label).ReadCrypt(cap);
			} catch (Exception e) {
				MessageBox.Show(e.Message, "Error Decrypting Value", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}
			try {
				Clipboard.SetText(value, TextDataFormat.UnicodeText);
			} catch (Exception e) {
				MessageBox.Show(e.Message, "Error Copying to Clipboard", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}

			return true;
		}

		public static string CapLabel(string cap) {
			switch (cap) {
			case "user":
				return "Username";
			case "pass":
				return "Password";
			case "snip":
				return "Snippet";
			default:
				return cap;
			}
		}

		private Dictionary<string, bool> LoadFiles(string dir) {
			Dictionary<string, bool> files = new Dictionary<string, bool>();
			foreach (string filename in Directory.GetFiles(dir, "master.digest", SearchOption.TopDirectoryOnly))
				files[Path.GetFileName(filename).ToLower()] = true;
			foreach (string t in new string[] { "label", "pass", "user", "snip" }) {
				foreach (string filename in Directory.GetFiles(dir, "*." + t, SearchOption.TopDirectoryOnly))
					files[Path.GetFileName(filename).ToLower()] = true;
			}
			return files;
		}

		private void SyncExternal() {
			if (Settings.Instance.Portable || !Settings.Instance.Valid ||
				!Settings.Instance.SyncExternal || Settings.Instance.SyncDrive == "") return;
			bool found = false;
			foreach (DriveInfo di in DriveInfo.GetDrives()) {
				if (di.ToString() == Settings.Instance.SyncDrive) {
					found = true;
					break;
				}
			}
			if (!found)
				return;
			try {
				int copied = 0, deleted = 0;

				string destexe = Settings.Instance.SyncStandalone ? "PLockPortableSA.exe" : "PLockPortable.exe";
				string otherdestexe = !Settings.Instance.SyncStandalone ? "PLockPortableSA.exe" : "PLockPortable.exe";
				string exedir = Path.GetDirectoryName(Application.ExecutablePath);
				string datadir = string.Format("{0}{1}", Settings.Instance.SyncDrive, Settings.SADirectory);

				if (File.Exists(string.Format("{0}{1}", Settings.Instance.SyncDrive, otherdestexe)))
					File.Delete(string.Format("{0}{1}", Settings.Instance.SyncDrive, otherdestexe));
				File.Copy(Application.ExecutablePath, string.Format("{0}{1}", Settings.Instance.SyncDrive, destexe), true);
				copied++;
				Settings.Instance.SaveToXML(Settings.Instance.SyncDrive);

				if (!Directory.Exists(datadir))
					Directory.CreateDirectory(datadir);

				Dictionary<string, bool> myfiles = LoadFiles(Settings.Instance.DataFolder);
				Dictionary<string, bool> theirfiles = LoadFiles(datadir);

				foreach (string file in myfiles.Keys) {
					File.Copy(string.Format("{0}{1}{2}", Settings.Instance.DataFolder, Path.DirectorySeparatorChar, file),
								string.Format("{0}{1}{2}", datadir, Path.DirectorySeparatorChar, file),
								true);
					copied++;
				}
				foreach (string todel in theirfiles.Keys) {
					if (!myfiles.ContainsKey(todel)) {
						File.Delete(string.Format("{0}{1}{2}", datadir, Path.DirectorySeparatorChar, todel));
						deleted++;
					}
				}

				string cryptdir = string.Format("{0}{1}", Settings.Instance.SyncDrive, "Crypt");
				string digestdir = string.Format("{0}{1}", Settings.Instance.SyncDrive, "Digest");
				string shadir = string.Format("{0}{1}{2}", digestdir, Path.DirectorySeparatorChar, "SHA");
				if (!Directory.Exists(cryptdir))
					Directory.CreateDirectory(cryptdir);
				if (!Directory.Exists(digestdir))
					Directory.CreateDirectory(digestdir);
				if (!Directory.Exists(shadir))
					Directory.CreateDirectory(shadir);
				ResourceManager mgr = new ResourceManager("PLock.Resources", GetType().Assembly);
				string filecontents = mgr.GetObject("plock").ToString();
				using (StreamWriter w = new StreamWriter(Settings.Instance.SyncDrive + "plock.pl", false, Encoding.ASCII)) {
					w.Write(filecontents);
				}
				copied++;
				filecontents = mgr.GetObject("CBC").ToString();
				using (StreamWriter w = new StreamWriter(string.Format("{0}{1}{2}", cryptdir, Path.DirectorySeparatorChar, "CBC.pm"), false, Encoding.ASCII)) {
					w.Write(filecontents);
				}
				copied++;
				filecontents = mgr.GetObject("Rijndael_PP").ToString();
				using (StreamWriter w = new StreamWriter(string.Format("{0}{1}{2}", cryptdir, Path.DirectorySeparatorChar, "Rijndael_PP.pm"), false, Encoding.ASCII)) {
					w.Write(filecontents);
				}
				copied++;
				filecontents = mgr.GetObject("PurePerl").ToString();
				using (StreamWriter w = new StreamWriter(string.Format("{0}{1}{2}", shadir, Path.DirectorySeparatorChar, "PurePerl.pm"), false, Encoding.ASCII)) {
					w.Write(filecontents);
				}
				copied++;
				using (StreamWriter w = new StreamWriter(Settings.Instance.SyncDrive + "autorun.inf", false, Encoding.ASCII)) {
					w.WriteLine("[autorun]");
					w.WriteLine("open={0}", destexe);
					w.WriteLine("icon={0},0", destexe);
				}
				copied++;

				notifyIcon.ShowBalloonTip(10000, "Removable Drive Sync Successful", string.Format("Copied {0} files, deleted {1}", copied, deleted), ToolTipIcon.Info);
			} catch (Exception e) {
				notifyIcon.ShowBalloonTip(10000, "Error Syncing to Drive", e.Message, ToolTipIcon.Error);
			}
		}
	}
}
